import { app, BrowserWindow, screen, Tray, Menu, ipcMain, dialog, shell, nativeTheme, net } from 'electron'
import { createRequire } from 'node:module'
import { fileURLToPath } from 'node:url'
import path from 'node:path'
import Store from 'electron-store';
import log from 'electron-log/main';
import fs from 'fs-extra';

Object.assign(console, log.functions);
const require = createRequire(import.meta.url)
const __dirname = path.dirname(fileURLToPath(import.meta.url))

// The built directory structure
//
// ├─┬─┬ dist
// │ │ └── index.html
// │ │
// │ ├─┬ dist-electron
// │ │ ├── main.js
// │ │ └── preload.mjs
// │
process.env.APP_ROOT = path.join(__dirname, '..')

// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x
export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']
export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron')
export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist')
export const EXE_PATH = path.dirname(app.getPath('exe'))
export const CORE_LIB_PATH = VITE_DEV_SERVER_URL ? path.join("..", "esearch-core", 'releases', "core.dll") : path.join(EXE_PATH, "core.dll")
export const DATA_PATH = path.join(app.getPath('userData'), "data")
export const DOWNLOAD_PATH = path.join(app.getPath('userData'), "downloads")
export const UPDATER_EXE_PATH = path.join(EXE_PATH, "updater.exe")

process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST
const { open, define } = require('ffi-rs')
const store = new Store({
  schema: {
    openAtLogin: {
      type: 'boolean',
      default: false,
    },
    dataPath: {
      type: "string",
      default: DATA_PATH
    },
    theme: {
      type: "string",
      default: "system"
    },
    resolvers: {
      type: "object",
      default: {
        "doc": "word",
        "docx": "word",
        "txt": "text",
        "xml": "xml",
        "html": "html",
        "conf": "text",
        "cnf": "text",
        "Dockerfile": "text",
        "sh": "text",
        "log": "text",
      },
    },
    updaterBaseUrl: {
      type: "array",
      default: ["https://gitee.com/lufuhu/esearch/releases/download", "https://github.com/lufuhu/esearch/releases/download"]
    }
  }
});
let win: BrowserWindow | null
let wins: Record<string, number> = {}
let winOptions = { width: 600, height: 35, maxHeight: 500, x: 0, y: -33 }
function autoScreen() {
  const primaryDisplay = screen.getPrimaryDisplay()
  const { width } = primaryDisplay.workAreaSize
  winOptions.width = Math.min(width, 600)
  winOptions.x = (width - winOptions.width) / 2
}
open({ library: 'core', path: CORE_LIB_PATH });
const lib = define({
  Run: {
    library: "core",
    retType: 0,
    paramsType: [0, 0],
    paramsValue: [],
  },
})
function createWindow() {
  autoScreen()
  win = new BrowserWindow({
    icon: path.join(process.env.VITE_PUBLIC, 'favicon.ico'),
    title: "ESearch",
    width: winOptions.width,
    height: winOptions.height,
    x: winOptions.x,
    y: winOptions.y,
    frame: false,
    resizable: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.mjs'),
    },
  })
  win.on("blur", () => {
    win?.setBounds({ x: winOptions.x, y: winOptions.y, width: winOptions.width, height: winOptions.height })
    win?.webContents.send('expand', false)
  })
  win.setAlwaysOnTop(true);
  win.setSkipTaskbar(true);
  trayMenu()
  createPreload();
  Menu.setApplicationMenu(null);
  win.webContents.on('did-finish-load', () => {
    setTheme("")
  })
  if (VITE_DEV_SERVER_URL) {
    // win?.webContents.openDevTools()
    win.loadURL(VITE_DEV_SERVER_URL)
  } else {
    // win.loadFile('dist/index.html')
    win.loadFile(path.join(RENDERER_DIST, 'index.html'))
  }
}
const trayMenu = () => {
  let tray = new Tray(path.join(process.env.VITE_PUBLIC, 'favicon.ico'),)
  const contextMenu = Menu.buildFromTemplate([
    { label: '打开', click: onExpand },
    {
      label: '添加', click: () => {
        newPage({ title: "添加", width: 700, height: 600, hash: "create", query: {} });
      }
    },
    {
      label: '设置', click: () => {
        newPage({ title: "设置", width: 700, height: 600, hash: "setting", query: {} })
      }
    },
    { label: '退出', click: onClose }
  ])
  tray.setToolTip('ESearch')
  tray.setContextMenu(contextMenu)
  tray.on("click", () => {
    onExpand()
  })
}
const onExpand = () => {
  win?.setBounds({ x: winOptions.x, y: 1, width: winOptions.width, height: winOptions.maxHeight })
  win?.focus()
  win?.webContents.send('expand', true)
}
const onClose = () => {
  app.exit(0);
}
const createPreload = () => {
  ipcMain.on("expand", () => {
    onExpand();
  })
  ipcMain.on("page", (_: any, options: any) => {
    newPage(options);
  })
  ipcMain.on("openPath", (_: any, filename: string) => {
    shell.openPath(filename)
  })
  ipcMain.on("setStore", (_: any, key: string, obj: any) => {
    switch (key) {
      case "openAtLogin":
        app.setLoginItemSettings({
          openAtLogin: obj,
        });
        break;
      case "theme":
        setTheme(obj)
        break;
    }
    store.set(key, obj);
  })
  ipcMain.handle("getStore", (_: any, key: string) => {
    if (key === "") {
      return store.store
    } else {
      return store.get(key)
    }
  })
  ipcMain.handle("core", (_: any, func: string, data: any, notify: boolean) => {
    return new Promise((resolve, reject) => {
      try {
        let res = lib.Run([func, data])
        if (!res) {
          reject("no data")
          console.error("no data", func, data)
          return
        }
        res = JSON.parse(res)
        if (notify) {
          if (res.code === 200) {
            resolve(res)
          } else {
            reject(res.error)
            console.error(res)
          }
        } else {
          resolve(res)
        }
      } catch (err: any) {
        reject(err.message)
        console.error(err)
      }
    })
  })
  ipcMain.handle("openDialog", (_: any, properties: any) => {
    return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() as BrowserWindow, { properties: properties })
  })
  ipcMain.handle("checkUpdate", () => {
    return checkUpdate()
  })
  ipcMain.on("download", (_: any, version: string) => {
    download(version)
  })
  ipcMain.on("updater", (_: any, version: string) => {
    relaunch(version)
  })
}
const checkUpdate = () => {
  let info = { version: app.getVersion(), latest_version: "" }
  return new Promise((resolve) => {
    let baseUrl: any = store.get("updaterBaseUrl")
    net.fetch(`${baseUrl[0]}/latest/latest.json`).then((response: any) => {
      if (response.ok) {
        response.json().then((res: any) => {
          if (res.version != info.version) {
            info.latest_version = res.version
          }
          resolve(info)
        }).catch((err: any) => {
          console.log(err)
          resolve(info)
        })
      } else {
        resolve(info)
      }
    }).catch((err: any) => {
      console.log(err)
      resolve(info)
    })
  })
}

const download = (version: string) => {
  let baseUrl: any = store.get("updaterBaseUrl")
  let zipPath = path.join(DOWNLOAD_PATH, `${version}.zip`)
  fs.emptyDirSync(DOWNLOAD_PATH)
  const request = net.request(`${baseUrl[0]}/${version}/windows_x86.zip`)
  request.on('response', (response: Electron.IncomingMessage) => {
    if (response.statusCode !== 200) {
      BrowserWindow.fromId(wins["setting"])?.webContents.send('updater', { type: "error", message: response.statusMessage })
      return
    }
    let total = Number(response.headers["content-length"] || 0)
    let finish = 0;
    response.on('data', (chunk: Buffer) => {
      fs.appendFileSync(zipPath, chunk)
      finish += chunk.length
      BrowserWindow.fromId(wins["setting"])?.webContents.send('updater', { type: "download", percent: parseInt((finish / total * 100).toString()), total: total })
    })
    response.on('end', () => {
      BrowserWindow.fromId(wins["setting"])?.webContents.send('updater', { type: "complete" })
    })
  })
  request.on('error', (err: Error) => {
    BrowserWindow.fromId(wins["setting"])?.webContents.send('updater', { type: "error", message: err.message })
  })
  request.end()
}
const relaunch = (version: string) => {
  BrowserWindow.getAllWindows().forEach(w => w.destroy());
  app.relaunch({ execPath: UPDATER_EXE_PATH, args: [app.getPath('exe'), path.join(DOWNLOAD_PATH, version + ".zip")] });
  app.quit();
}
const setTheme = (value: any) => {
  if (!value) {
    value = store.get("theme", "system")
  }
  nativeTheme.themeSource = value
  if (value == 'system') {
    value = nativeTheme.shouldUseDarkColors ? 'dark' : 'light';
  }
  let isDark = value === 'dark'
  win?.webContents.send('dark', isDark)
  for (let i in wins) {
    let child = BrowserWindow.fromId(wins[i])
    child?.webContents.send('dark', isDark)
  }
}

const newPage = (options: any) => {
  if (wins[options.hash]) {
    let child = BrowserWindow.fromId(wins[options.hash])
    if (child) {
      child.focus()
      return
    }
  }
  let resizable, minimizable = true;
  if (options.resizable === true) {
    resizable = true;
    minimizable = true;
  } else {
    resizable = false;
    minimizable = false;
  }
  let child = new BrowserWindow({
    icon: path.join(process.env.VITE_PUBLIC, 'favicon.ico'),
    title: options.title,
    show: false,
    width: options.width,
    height: options.height,
    resizable: resizable,
    minimizable: minimizable,
    webPreferences: {
      preload: path.join(__dirname, 'preload.mjs'),
    },
  })
  wins[options.hash] = child.id;
  child.once('ready-to-show', () => {
    child.show()
  })
  child.on('closed', () => {
    delete wins[options.hash]
  })
  if (VITE_DEV_SERVER_URL) {
    // child?.webContents.openDevTools()
    child.loadURL(VITE_DEV_SERVER_URL + `?${new URLSearchParams(options.query).toString()}/#/${options.hash}`)
  } else {
    child.loadFile(path.join(RENDERER_DIST, 'index.html'), { hash: options.hash, query: options.query })
  }
}
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
    win = null
  }
})

app.on('activate', () => {
  // On OS X it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (BrowserWindow.getAllWindows().length === 0) {
    createWindow()
  }
})
const lock = app.requestSingleInstanceLock()
if (lock) {
  app.on('second-instance', () => {
    onExpand()
  })
  app.whenReady().then(createWindow)
} else {
  app.quit()
}